//                        ElgProg
//
//    A cheap DIY Arduino based Universal Programmer
//
//    Copyright (C) Elgen };-P aka Morten Overgaard 2015
//
//    This file is part of ElgProg.
//
//    ElgProg is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    ElgProg is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with ElgProg. If not, see <http://www.gnu.org/licenses/>.

#include "Arduino.h"
#include "EpromLib.h"

// Define an 27512 EPROM
unsigned long addressesV = 65536;
//                     A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15
int addressPinsV[] = { 40, 38, 36, 34, 32, 30, 28, 26, 29, 31,  37,  33,  24,  27,  25,  22, 0 };
//                     D0, D1, D2, D3, D4, D5, D6, D7
int dataPinsV[]    = { 42, 44, 46, 49, 47, 45, 43, 41, 0 };
int cePinsV[]      = { 39, 0 };
int oePinsV[]      = { 35, 0 };

Eprom::Eprom(LiquidCrystal* lcd, unsigned long dataBufferSize, byte* currentBuffer, byte* nextBuffer) 
	: lcd(lcd), dataBufferSize(dataBufferSize), currentBuffer(currentBuffer), nextBuffer(nextBuffer)
{}

Eprom::~Eprom()
{}

void Eprom::readConfig()
{
  //debugPrint("Waiting", 1000);
  while(Serial.available() == 0);
  Serial.readBytes(currentBuffer, dataBufferSize);
  //debugPrint("Serial data read", 1000);
  int bufferIndex = 0;
  int confIndex = 0;  
  addresses = 1;
  while(currentBuffer[bufferIndex] != (byte)0)
  {
    addressPins[confIndex++] = (int)currentBuffer[bufferIndex++];
    addresses *= (unsigned long)2;
  }
  addressPins[confIndex] = 0;

  confIndex = 0;
  bufferIndex++;
  while(currentBuffer[bufferIndex] != (byte)0)
  {
    dataPins[confIndex++] = (int)currentBuffer[bufferIndex++];
  }
  dataPins[confIndex] = 0;
  
  confIndex = 0;
  bufferIndex++;
  while(currentBuffer[bufferIndex] != (byte)0)
  {
    cePins[confIndex++] = (int)currentBuffer[bufferIndex++];
  }
  cePins[confIndex] = 0;
  
  confIndex = 0;
  bufferIndex++;
  while(currentBuffer[bufferIndex] != (byte)0)
  {
    oePins[confIndex++] = (int)currentBuffer[bufferIndex++];
  }
  oePins[confIndex] = 0;
  switchBuffers();
  
  //debugPrint("conf read", 1000);  
  Serial.write("conf read\n");
}

void Eprom::verifyConf()
{
  verifyConfArray(addressPins, addressPinsV, "A");
  verifyConfArray(dataPins, dataPinsV, "D");
  verifyConfArray(cePins, cePinsV, "CE");
  verifyConfArray(oePins, oePinsV, "OE");
}

void Eprom::verifyConfArray(int* confArray, int* confArrayV, String txt)
{
  int i = 0;
  while(confArray[i] != 0)
  {
    debugPrint(txt + " " + confArray[i], 1000);
    if(confArray[i] != confArrayV[i])
      debugPrint(txt + " " + i + " " + confArray[i] + " " + confArrayV[i], 1000);
    i++;
  }
}

void Eprom::readEprom()
{
  setPinMode(OUTPUT, addressPins);
  setPinMode(OUTPUT, cePins);
  setPinMode(OUTPUT, oePins);
  setPinMode(INPUT, dataPins);

  unsigned long addressCounter = 0;
  unsigned long bytesRead = 0;
  setPins(LOW, cePins);
  setPins(HIGH, oePins);
  lcd->clear();
  lcd->print("Bytes read:");
  while(addressCounter < addresses)
  {
    lcd->setCursor(0, 1);
    lcd->print(addressCounter);
    bytesRead = readToBuffer(addressCounter, addresses, dataBufferSize, currentBuffer);
    Serial.write(currentBuffer, bytesRead);
    addressCounter += bytesRead;
    switchBuffers();
  }

  lcd->setCursor(0, 1);
  lcd->print(addressCounter);
  setPins(HIGH, oePins);
  setPins(HIGH, cePins);
  lcd->setCursor(0, 0);
  lcd->print("Done reading");
  //delay(2000);
}

void Eprom::switchBuffers()
{
  byte* temp = currentBuffer;
  currentBuffer = nextBuffer;
  nextBuffer = temp;  
}

unsigned long Eprom::readToBuffer(unsigned long firstAddress, unsigned long allAddresses, unsigned long bufferSize, byte* theBuffer)
{
  unsigned long bufferCounter = 0;
  unsigned long addressCounter = firstAddress;
  while(bufferCounter < bufferSize && addressCounter < allAddresses)
  {
    writeAddress(addressCounter, addressPins);
    setPins(LOW, oePins);
    theBuffer[bufferCounter] = readData8Bit(dataPins);
    setPins(HIGH, oePins);
    bufferCounter++;
    addressCounter++;
  }
  return bufferCounter;
}

void Eprom::setPinMode(int mode, int* pins)
{
  int count = 0;
  while(pins[count] != 0)
    pinMode(pins[count++], mode);
}

void Eprom::setPins(int value, int* pins)
{
  int count = 0;
  while(pins[count] != 0)
    digitalWrite(pins[count++], value);
}

byte Eprom::readData8Bit(int* pins)
{
  byte acc = 0;
  unsigned long bitCount = 0;
  while(pins[bitCount] != 0)
  {
    acc |= ((unsigned long)(digitalRead(pins[bitCount]) == LOW ? 0 : 1)) << bitCount;
    bitCount++;
  }
  return acc;
}

void Eprom::writeAddress(unsigned long address, int* pins)
{
  unsigned long addressAcc = address;
  int bitCount = 0;
  while(pins[bitCount] != 0)
  {
    digitalWrite(pins[bitCount++], addressAcc % 2 == 0 ? LOW : HIGH);
    addressAcc = addressAcc >> (unsigned long)1;
  }  
}
